package com.example.concurrent;

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.LockSupport;

public class AQS02_ReentrantLock {
    private static final ConcurrentLinkedQueue<Thread> list = new ConcurrentLinkedQueue<>();
    private static boolean lock = true; //标识获取到锁
    private static boolean freeLock = false; //锁的释放信号
    private static int counter;

    /**
     * 手动模拟实现一个互斥锁，需要保证任意时刻只有一个线程访问临界资源或执行被lock包裹的代码块
     * 突破口：参考Synchronized轻量级锁升级重量级锁时的过度锁 -> 自旋锁
     */

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            Thread thread = new Thread(() -> {
                Thread.currentThread().setName("线程" + finalI + "： ");
                //不加锁(预期结果为1万)
//                for (int j = 0; j < 1000; j++) {
//                    counter++; //此操作分为读(read)、修改(use)、写回主存(write)三步；这三步并非原子性操作（JMM不保证它们连续执行）
//                }
                //TODO 加锁(还是会有错误)，实现的逻辑并不严谨
                reentrantLock();
            });
            thread.start();
        }
        Thread.sleep(2000);
        System.out.println("操作结束：" + counter);
        System.out.println("Lock:" + lock + "; freeLock: " + freeLock);

    }

    public static void reentrantLock() {
        /**
         * 思考：竞争锁失败的线程放在while循环中进行自旋，直到获取锁的线程执行完程序释放锁，当前线程获取到锁
         */
        while (!lock) {
            //思考：若有成百上千的线程都在自旋，是不是会浪费CPU资源
            /**
             * Thread.yield(); //考虑让出CPU使用权；结论：并不可行
             * Thread.sleep(1000); //考虑让线程睡眠；结论：并不可行，到底睡眠多久把握不住
             */
            /**
             * 可行方案：阻塞线程，在接收到锁释放信号后唤醒一个阻塞线程
             */
            //使用链表或队列保存即将被阻塞的线程，在下次被唤醒的时候使用
            list.add(Thread.currentThread());
//            System.out.println(Thread.currentThread().getName() + "阻塞了");
            //阻塞线程,减少不必要的自旋
            LockSupport.park();
        }
        //独占，更新锁的状态为false
        lock = false;
//        System.out.println(Thread.currentThread().getName() + "得到锁了");
        //业务逻辑***
        for (int j = 0; j < 1000; j++) {
            counter++; //此操作分为读(read)、修改(use)、写回主存(write)三步；这三步并非原子性操作（JMM不保证它们连续执行）
        }
        //逻辑执行完毕，更新释放锁的信号
        freeLock = true;
//        System.out.println(Thread.currentThread().getName() + "释放了");
        //唤醒阻塞
        freeLockMonitor();
    }

    //唤醒阻塞线程的方法
    public static void freeLockMonitor() {
        try {
            if (freeLock && !list.isEmpty()) {
                //唤醒下一个线程继续自旋
                Thread t = list.peek();
                list.remove(t);

                freeLock = false;
                //更新lock锁状态，使唤醒的线程获得锁，即退出自旋状态执行业务逻辑
                lock = true;
                LockSupport.unpark(t);
            }
        } finally {
            freeLock = false;
            //更新lock锁状态，使唤醒的线程获得锁，即退出自旋状态执行业务逻辑
            lock = true;
        }
    }
}
